﻿using System;
using System.Collections;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Threading;
using System.Xml.Serialization;
using Microsoft.TeamFoundation.VersionControl.Client;
using TfsBranchLib2.View;
using TfsComLib.TfsBranchLib2;
using WpfAdvControls;
using WpfAdvControls.Component;
using WpfAdvControls.Plate;

namespace TfsBranchLib2.Model
{
    public class BranchedHistoryContext : ComponentBase, IBranchedHistory
    {
        private static BranchedHistoryContext branchedHistoryContext;

        /// <summary>
        ///     Source Control Base Path (one of the branches)
        /// </summary>
        public readonly ISourceControlBasePath sourceControlBasePath;

        /// <summary>
        ///     Source Control Branch Provider
        /// </summary>
        public readonly ISourceBranches sourceControlBranchProvider;

        private readonly object syncIsLoaded = new object();


        /// <summary>
        ///     Start process history query command stub
        /// </summary>
        [XmlIgnore] public LibCommand GenerateHistoryListCommand;

        private bool isLoaded;


        /// <summary>
        ///     (if it's no filter, QueryType=SingleSubPath)
        ///     Path within branches
        /// </summary>
        private string pathWithinBranches;

        /// <summary>
        ///     property Progress back (ProgressBar.Value)
        /// </summary>
        private double progress;

        /// <summary>
        ///     Type of query: single path or filter
        /// </summary>
        private BranchedHistoryQueryType queryType;


        /// <summary>
        ///     back of property SelectedBranchesCache Stub for selected branches, branch list provider
        /// </summary>
        private ISelectedBranchesCache selectedBranchesCache;

        /// <summary>
        ///     (if it's no filter, QueryType=SingleSubPath)
        ///     singlePath - Full server path for history query
        ///     back of SinglePath property
        /// </summary>
        private string singlePath;

        /// <summary>
        ///     Stub for SourceControl Filter
        /// </summary>
        private ISourceControlFilter sourceControlFilter;

        /// <summary>
        ///     Stop process history query command stub
        /// </summary>
        [XmlIgnore] public LibCommand StopQueryCommand;

        /// <summary>
        ///     Public constructor
        /// </summary>
        protected BranchedHistoryContext()
        {
            SetError("Not initialized");
            sourceControlBasePath = new SourceControlBasePath();
            sourceControlBranchProvider = new SourceControlBranchProvider(sourceControlBasePath);
            SelectedBranchesCache = new SelectedBranchesCache {SourceBranchesProvider = sourceControlBranchProvider};
            QueryType = BranchedHistoryQueryType.SingleSubPath;
            SourceControlFilter = new SourceControlFilter(sourceControlBasePath);
            GenerateHistoryListCommand = new LibCommand(GenerateHistoryListRun, true, ProcessCompleted);
            StopQueryCommand = new LibCommand(StopQuery, true, ProcessCanceled);
            HistoryList = new MTObservableCollection<HistoryItem>();
            QueryTypeResults = BranchedHistoryQueryType.SingleSubPath;
            RecentBranchSets = new RecentBranchSets();
        }

        public static BranchedHistoryContext DefaultBranchedHistoryContext
        {
            get
            {
                if (branchedHistoryContext == null)
                {
                    branchedHistoryContext = new BranchedHistoryContext();
                }
                return branchedHistoryContext;
            }
        }

        /// <summary>
        ///     ProgressBar.Value
        /// </summary>
        public double Progress
        {
            get { return progress; }
            set
            {
                progress = value;
                NotifyPropertyChanged("Progress");
            }
        }


        /// <summary>
        ///     Results of a query of selecting changes from the TFS
        /// </summary>
        public MTObservableCollection<HistoryItem> HistoryList { get; set; }

        /// <summary>
        ///     SelectedBranchesCache Stub for selected branches, branch list provider
        /// </summary>
        public ISelectedBranchesCache SelectedBranchesCache
        {
            get { return selectedBranchesCache; }
            set
            {
                selectedBranchesCache = value;
                NotifyPropertyChanged("SelectedBranchesCache");
            }
        }

        /// <summary>
        ///     Query type: SinglePath or Filter
        /// </summary>
        public BranchedHistoryQueryType QueryType
        {
            get { return queryType; }
            set
            {
                queryType = value;
                NotifyPropertyChanged("QueryType");
            }
        }

        /// <summary>
        ///     SourceControl Filter. A number of paths within branches
        /// </summary>
        public ISourceControlFilter SourceControlFilter
        {
            get { return sourceControlFilter; }
            set
            {
                sourceControlFilter = value;
                NotifyPropertyChanged("SourceControlFilter");
            }
        }

        /// <summary>
        ///     (if it's no filter, QueryType=SingleSubPath)
        ///     SinglePath - Full server path for history query
        /// </summary>
        public string SinglePath
        {
            get { return singlePath; }
            set
            {
                singlePath = value;
                UpdatePath(singlePath);
                NotifyPropertyChanged("SinglePath");
            }
        }

        /// <summary>
        ///     Path within branch. Cutted, starting where branch path ends
        /// </summary>
        public string PathWithinBranches
        {
            get { return pathWithinBranches; }
            set
            {
                pathWithinBranches = value;
                NotifyPropertyChanged("PathWithinBranches");
            }
        }

        /// <summary>
        ///     Occurs when query completed
        /// </summary>
        public event EventHandler OnProcessCompleted;

        /// <summary>
        ///     Occurs when query Canceled
        /// </summary>
        public event EventHandler OnProcessCanceled;

        public override void OnSettingsView(Window owner)
        {
            var settings = new SourceControlBasePathSettingsWindow(owner, sourceControlBasePath);
            settings.ShowDialog();
        }

        public event EventHandler OnProcessStarted;


        public BranchedHistoryQueryType QueryTypeResults { get; set; }

        public IRecentBranchSets RecentBranchSets { get; set; }


        /// <summary>
        ///     Stop/Cancel query
        /// </summary>
        private void StopQuery()
        {
            GenerateHistoryListCommand.Abort();
        }


        /// <summary>
        ///     Main procedure
        /// </summary>
        private void GenerateHistoryListRun()
        {
            try
            {
                IsError = false;
                BeforeStart();
                ProcessStarted();
                HistoryList.Clear();
                // For specializing history processing
                // record query type for current results
                // due QueryType can be changed after processing by user input


                QueryTypeResults = QueryType;
                Progress = 0;

                switch (QueryType)
                {
                    case BranchedHistoryQueryType.SingleSubPath:
                        SingleQueryRun();
                        break;
                    case BranchedHistoryQueryType.BranchFilter:
                        BranchFilterQueryRun();
                        break;
                    default:
                        SetError("Error query type");
                        break;
                }
            }
            catch (Exception ex)
            {
                SetError(ex);
            }
        }

        private void BranchFilterQueryRun()
        {
            if (SourceControlFilter.SubPathList.Count < 1)
            {
                throw new Exception("Filter is not set for a query");
            }
            var vcs = SelectedBranchesCache.SourceBranchesProvider.VersionControlServer;
            if (vcs == null) throw new Exception("Client Version Control Server instance not initialized");
            var listOfSelectedBranches =
                SelectedBranchesCache.Branches.Where((BranchData branch) => branch.IsSelected).ToList();
            if (listOfSelectedBranches.Count < 1)
            {
                throw new Exception("No branches selected");
            }
            RecentBranchSets.ApplyPrecedent(new BranchSet((listOfSelectedBranches.ToArray())));
            var progressPoints = listOfSelectedBranches.Count*((double) (SourceControlFilter.SubPathList.Count));
            var stepValue = 100/progressPoints;
            foreach (var branch in listOfSelectedBranches)
            {
                foreach (var subPath in SourceControlFilter.SubPathList)
                {
                    AddInnerPathHistory(branch, subPath, vcs, false);
                    Progress += stepValue;
                }
            }
        }

        private void SingleQueryRun()
        {
            if (string.IsNullOrWhiteSpace(SinglePath))
            {
                throw new Exception("Path is not set for a query");
            }
            var vcs = SelectedBranchesCache.SourceBranchesProvider.VersionControlServer;
            if (vcs == null) throw new Exception("Client Version Control Server instance not initialized");
            var listOfSelectedBranches =
                SelectedBranchesCache.Branches.Where((BranchData branch) => branch.IsSelected).ToList();
            if (listOfSelectedBranches.Count < 1)
            {
                throw new Exception("No branches selected");
            }
            RecentBranchSets.ApplyPrecedent(new BranchSet((listOfSelectedBranches.ToArray())));
            var stepValue = 100/((double) listOfSelectedBranches.Count);
            var isQueryChangesForPath = (vcs.GetItem(SinglePath, VersionSpec.Latest).ItemType == ItemType.File);
            foreach (var branch in listOfSelectedBranches)
            {
                AddInnerPathHistory(branch, PathWithinBranches, vcs, isQueryChangesForPath);
                Progress += stepValue;
            }
        }

        private void AddInnerPathHistory(BranchData branch, string pathWithin, VersionControlServer vcs,
            bool isQueryChangesForPath)
        {
            var queriedPath = branch.ServerPath + "/" + pathWithin;
            var history = vcs.QueryHistory(queriedPath, VersionSpec.Latest,
                0, RecursionType.Full, null, null, VersionSpec.Latest, 100000, true, true, true);
            ProcessHistoryList(branch, history, queriedPath, isQueryChangesForPath);
        }


        private void ProcessHistoryList(BranchData branch, IEnumerable history, string path, bool isQueryChangesForPath)
        {
            foreach (var historyObject in history)
            {
                //logger.Debug(historyObject.GetType().FullName);
                var changeset = historyObject as Changeset;
                var changes = string.Empty;
                if (changeset == null) continue;
                if (isQueryChangesForPath)
                {
                    var query = from cs in changeset.Changes
                        where cs.Item.ServerItem == path
                        select cs.ChangeType;
                    var changeType = query.FirstOrDefault();
                    changes = changeType.ToString();
                }
                // if branch filter query 
                // and if changeset is already in the list
                // then go to next (not add to HistoryList existing record)
                if ((queryType == BranchedHistoryQueryType.BranchFilter)
                    && (HistoryList.Any(hisItem => hisItem.ID == changeset.ChangesetId)))
                {
                    continue;
                }
                var item = new HistoryItem
                {
                    ID = changeset.ChangesetId,
                    // Remove domain prefix
                    Committer = Regex.Match(changeset.Committer, @"([^\\/])+$").Value,
                    Comment = changeset.Comment.Trim(),
                    RegistryDate = changeset.CreationDate,
                    ItemType = HistoryItemType.Changeset,
                    Branch = branch,
                    Changes = changes
                };
                HistoryList.Add(item);
            }
        }

        /// <summary>
        ///     Refresh default branch, subpath for dedicated path in TFS VersionControl
        ///     Assume the method should be using for QueryType=Single
        /// </summary>
        /// <param name="newPath"></param>
        private void UpdatePath(string newPath)
        {
            try
            {
                IsError = !SelectedBranchesCache.SourceBranchesProvider.EnsureTrunkSet(newPath);
                ErrorMessage = "No appropriate branches for the path";
                if (newPath == null) return;
                // Set root branch ( trunk ) for serializing context of recent branches
                // TODO: this is bad logic, that i cant understand now
                if (SelectedBranchesCache.SourceBranchesProvider.BasePath != null)
                {
                    RecentBranchSets.LoadForTrunk(SelectedBranchesCache.SourceBranchesProvider.BasePath);
                    var vcs = SelectedBranchesCache.SourceBranchesProvider.VersionControlServer;
                    var isFilePath = (vcs.GetItem(newPath, VersionSpec.Latest).ItemType == ItemType.File);
                    SelectedBranchesCache.Refresh();
                }
                if (IsError) return;

                BranchData selectedBranch;
                if (SelectedBranchesCache.SetDefaultBranchForPath(newPath, out selectedBranch))
                {
                    if (newPath.Length > selectedBranch.ServerPath.Length + 1)
                    {
                        PathWithinBranches = newPath.Substring(selectedBranch.ServerPath.Length + 1);
                    }
                    else
                    {
                        PathWithinBranches = string.Empty;
                    }
                }
                else
                {
                    PathWithinBranches = string.Empty;
                }
            }
            catch (NullReferenceException)
            {
                SetError("Can't initialize data for the path");
            }
            catch (Exception ex)
            {
                SetError(ex);
            }
        }

        /// <summary>
        ///     Occurs when query starts
        /// </summary>
        public event EventHandler BeforeStartQuery;

        /// <summary>
        ///     Before main process
        /// </summary>
        private void BeforeStart()
        {
            if (BeforeStartQuery != null)
            {
                Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, BeforeStartQuery, this, null);
            }
        }

        /// <summary>
        ///     Fire on process completed
        /// </summary>
        private void ProcessCompleted()
        {
            if (OnProcessCompleted != null)
            {
                OnProcessCompleted(this, null);
            }
        }

        /// <summary>
        ///     Fire on process canceled
        /// </summary>
        private void ProcessCanceled()
        {
            if (OnProcessCanceled != null)
            {
                OnProcessCanceled(this, null);
            }
        }

        /// <summary>
        ///     Fire on process started
        /// </summary>
        private void ProcessStarted()
        {
            if (OnProcessStarted != null)
            {
                OnProcessStarted(this, null);
            }
        }

        internal void CheckLoaded()
        {
            lock (syncIsLoaded)
            {
                if (isLoaded) return;
                var rootPath = sourceControlBranchProvider.BasePath;
                RecentBranchSets.LoadForTrunk(rootPath);
                isLoaded = true;
            }
        }
    }
}